#include "CRenderDriverD3D11.h"
#include "WinDevice.h"
#include "SColorf.h"
#include "CShaderManager.h"
#include "DDefaultShaders.h"
#include "CTexturesCache.h"
#include "ILDXException.h"
#include "CRenderStateConsole.h"

struct SDefaultShadersVSPS
{
	char* ProgName;
	mt::wchar* VsFileName;
	char* VSEntryPort;
	mt::wchar* PsFileName;
	char* PSEntryPort;
};
static int NumOfDefaultVSPS = 1;
static SDefaultShadersVSPS DefaultShaderVSPS[]=
{
	{
		_DEFAULT_PROGRAM_PHONG_STD,
		_DEFAULT_PROGRAM_PHONG_STD_VS_PATH,
		_DEFAULT_PROGRAM_PHONG_STD_VS_ENTRYPORT,
		_DEFAULT_PROGRAM_PHONG_STD_PS_PATH,
		_DEFAULT_PROGRAM_PHONG_STD_PS_ENTRYPORT
	}
};

namespace ldx
{
	namespace render
	{
		CRenderDriverD3D11* createCRenderDriverD3D11(device::WinDevice* device,
			const mt::dimension2du& screenSize,
			HWND window, unsigned int bits,
			bool fullscreen, bool depthbuffer, bool stencibuffer)
		{
			CRenderDriverD3D11* driver = new CRenderDriverD3D11(device, window,screenSize,bits,fullscreen,depthbuffer,stencibuffer);
			if (!driver)
				device::WinDevice::onError(0, "create render driver d3d11 failed!");
			return driver;
		}
	}
}

ldx::render::CRenderDriverD3D11::CRenderDriverD3D11(device::WinDevice * device, HWND hwnd,
	mt::dimension2du screensize, uint bits, bool fullscreen, bool depthbuffer, bool stencilbuffer)
	:Device(device),
	HInst(nullptr),
	HWnd(hwnd),
	DriverType(D3D_DRIVER_TYPE_NULL),
	FeatureLevel(D3D_FEATURE_LEVEL_11_0),
	Pd3dDevice(nullptr),
	PImmediateContext(nullptr),
	PSwapChain(nullptr),
	PRenderTargetView(nullptr),
	Bits(bits),
	IsDepthBuffer(depthbuffer),
	IsStencilBuffer(stencilbuffer),
	IsFullScreen(fullscreen),
	ScreenSize(screensize),
	CameraDirection(),
	CameraPosition()
{
	if (initD3D11()<0)
	{
		device::WinDevice::onError(0, "initD3D11() failed!");
	}
	if (0 != initCaches())
	{
		device::WinDevice::onError(1, "initCaches() failed!");
	}
	if (initShaders() != 0)
	{
		device::WinDevice::onError(1, "initShaders() failed!");
	}
	//init render console
	RenderConsole = new CRenderStateConsole(getDeviceMap());
	RenderConsole->serRasterViewPort((float)screensize.Width, (float)screensize.Height);
}

ldx::render::CRenderDriverD3D11::~CRenderDriverD3D11()
{
	if (RenderConsole)
		RenderConsole->drop();
	if (ShaderManager)
		ShaderManager->drop();
	deleteD3D11();
}

bool ldx::render::CRenderDriverD3D11::clear(E_CLEAR_FLAG flag, SColorf & color, float depth , uint stencil)
{
	if (flag & ECF_COLOR_BUFFER)
		PImmediateContext->ClearRenderTargetView(PRenderTargetView, &color[0]);
	if ((flag & ECF_DEPTH_BUFFER)&&IsDepthBuffer)
		PImmediateContext->ClearDepthStencilView(PDepthStencilView, D3D11_CLEAR_DEPTH, depth, stencil);
	if ((flag & ECF_STENCIL_BUFFER)&&IsStencilBuffer)
		PImmediateContext->ClearDepthStencilView(PDepthStencilView, D3D11_CLEAR_STENCIL, depth, stencil);
	return true;
}

bool ldx::render::CRenderDriverD3D11::clearColorBuffer(SColorf & color)
{
	PImmediateContext->ClearRenderTargetView(PRenderTargetView, &color[0]);
	return true;
}

bool ldx::render::CRenderDriverD3D11::clearDepthBuffer(float depth)
{
	if (IsDepthBuffer)
	{
		PImmediateContext->ClearDepthStencilView(PDepthStencilView, D3D11_CLEAR_DEPTH, depth, 0);
		return true;
	}

	return false;
}

bool ldx::render::CRenderDriverD3D11::clearStencilBuffer(uint stencil)
{
	if (IsStencilBuffer)
	{
		PImmediateContext->ClearDepthStencilView(PDepthStencilView, D3D11_CLEAR_STENCIL, 1.0f, stencil);
		return true;
	}
	return false;
}

bool ldx::render::CRenderDriverD3D11::swapChain()
{
	HRESULT  hr= PSwapChain->Present(0, 0);
	if (FAILED(hr))
		throw os::ILDXException(L"PSwapChain->Present failed with hresult = " + hr);
	return true;
}

void ldx::render::CRenderDriverD3D11::setTransform(E_TRANFORM_MATRIX transform, mt::mat4 & matrix)
{
	TransformMatrix[transform] = matrix;
}

mt::mat4 & ldx::render::CRenderDriverD3D11::getTransform(E_TRANFORM_MATRIX transform)
{
	return TransformMatrix[transform];
}

void ldx::render::CRenderDriverD3D11::setViewPosition(const mt::vec3f & pos)
{
	CameraPosition = pos;
}

mt::vec3f & ldx::render::CRenderDriverD3D11::getViewPosition()
{
	return CameraPosition;
}

void ldx::render::CRenderDriverD3D11::setViewDirection(const mt::vec3f & dir)
{
	CameraDirection = dir;
}

mt::vec3f & ldx::render::CRenderDriverD3D11::getViewDirection()
{
	return CameraDirection;
}

mt::vector2dui ldx::render::CRenderDriverD3D11::getScreenSize()
{
	return mt::vector2dui();
}

mt::vector2di ldx::render::CRenderDriverD3D11::getCurrentRenderTargetSize()
{
	return mt::vector2di(ScreenSize.Width, ScreenSize.Height);
}

ldx::SDeviceMap ldx::render::CRenderDriverD3D11::getDeviceMap()
{
	SDeviceMap dm;
	dm.Window.HWnd = HWnd;
	dm.Window.D3DDevice = Pd3dDevice;
	dm.Window.D3DDeviceContext = PImmediateContext;
	return dm;
}

ldx::render::CShaderManager * ldx::render::CRenderDriverD3D11::getShaderManager()
{
	return ShaderManager;
}

ldx::render::CTexturesCache * ldx::render::CRenderDriverD3D11::getTexturesCache()
{
	return TexturesCache;;
}

ldx::render::CTexture * ldx::render::CRenderDriverD3D11::addTexture(std::string & pathName)
{
	assert(this->TexturesCache&&"TexturesCache not inited.");
	return TexturesCache->addTexture(pathName);
}

int ldx::render::CRenderDriverD3D11::setRasterState(E_RASTER_STATE_TYPES rasterstate)
{
	return RenderConsole->setRasterState(rasterstate);
}

int ldx::render::CRenderDriverD3D11::serRasterViewPort(float width, float height, float topleftX, float topleftY, float depthmin, float depthmax)
{
	return RenderConsole->serRasterViewPort(width, height, topleftX, topleftY, depthmin, depthmax);
}

ldx::render::SViewPort ldx::render::CRenderDriverD3D11::getRasterViewPort()
{
	return RenderConsole->getRasterViewPort();
}

long ldx::render::CRenderDriverD3D11::initD3D11()
{
	HRESULT hr = S_OK;

	RECT rc;
	GetClientRect(HWnd, &rc);
	UINT width = rc.right - rc.left;
	UINT height = rc.bottom - rc.top;

	UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)  
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	D3D_DRIVER_TYPE driverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE,
	};
	UINT numDriverTypes = ARRAYSIZE(driverTypes);

	D3D_FEATURE_LEVEL featureLevels[] =
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
	};
	UINT numFeatureLevels = ARRAYSIZE(featureLevels);

	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory(&sd, sizeof(sd));
	sd.BufferCount = 1;
	sd.BufferDesc.Width = width;
	sd.BufferDesc.Height = height;
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.OutputWindow = HWnd;
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.Windowed = TRUE;

	for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
	{
		DriverType = driverTypes[driverTypeIndex];
		
		hr = D3D11CreateDeviceAndSwapChain(nullptr, DriverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
			D3D11_SDK_VERSION, &sd, &PSwapChain, &Pd3dDevice, &FeatureLevel, &PImmediateContext);
	
           
		if (SUCCEEDED(hr))
			break;
	}
	if (FAILED(hr))
		return hr;

	// Create a render target view
	ID3D11Texture2D* pBackBuffer = nullptr;
	hr = PSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
	if (FAILED(hr))
		return hr;
	hr = Pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &PRenderTargetView);
	pBackBuffer->Release();
	if (FAILED(hr))
		return hr;
	PDepthStencilView = nullptr;
	if (IsDepthBuffer)
	{
		// Create depth stencil texture
		D3D11_TEXTURE2D_DESC descDepth;
		ZeroMemory(&descDepth, sizeof(descDepth));
		descDepth.Width = ScreenSize.Width;
		descDepth.Height = ScreenSize.Height;
		descDepth.MipLevels = 1;
		descDepth.ArraySize = 1;
		descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
		descDepth.SampleDesc.Count = 1;
		descDepth.SampleDesc.Quality = 0;
		descDepth.Usage = D3D11_USAGE_DEFAULT;
		descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
		descDepth.CPUAccessFlags = 0;
		descDepth.MiscFlags = 0;
		hr = Pd3dDevice->CreateTexture2D(&descDepth, nullptr, &PDepthStencil);
		if (FAILED(hr))
			return hr;

		// Create the depth stencil view
		D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
		ZeroMemory(&descDSV, sizeof(descDSV));
		descDSV.Format = descDepth.Format;
		descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
		descDSV.Texture2D.MipSlice = 0;
		hr = Pd3dDevice->CreateDepthStencilView(PDepthStencil, &descDSV, &PDepthStencilView);
		if (FAILED(hr))
			return hr;
	}
	
	//bind back buffer to the pipeline.
	PImmediateContext->OMSetRenderTargets(1, &PRenderTargetView, PDepthStencilView);
	//rasiter
	// Setup the viewport
	//D3D11_VIEWPORT vp;
	//ZeroMemory(&vp, sizeof(vp));
	//vp.Width = (FLOAT)width;
	//vp.Height = (FLOAT)height;
	//vp.MinDepth = 0.0f;
	//vp.MaxDepth = 1.0f;
	//vp.TopLeftX = 0;
	//vp.TopLeftY = 0;
	//PImmediateContext->RSSetViewports(1, &vp);

	return S_OK;
}

long ldx::render::CRenderDriverD3D11::initShaders()
{
	ShaderManager = new CShaderManager(this);
	D3D11_INPUT_ELEMENT_DESC vspslayerout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }
	};
	//init default shaders.
	{
		//vp ps
		{
			for (int i = 0; i < NumOfDefaultVSPS; ++i)
			{
				ShaderManager->addProgramVPS(DefaultShaderVSPS[i].ProgName,
					DefaultShaderVSPS[i].VsFileName,
					DefaultShaderVSPS[i].VSEntryPort,
					3, vspslayerout,
					DefaultShaderVSPS[i].PsFileName,
					DefaultShaderVSPS[i].PSEntryPort);
			}
		}

	}
	return 0;
}

long ldx::render::CRenderDriverD3D11::initCaches()
{
	SDeviceMap dm = getDeviceMap();
	//! textures cache.
	TexturesCache = new CTexturesCache(dm);

	return 0;
}

void ldx::render::CRenderDriverD3D11::deleteD3D11()
{
	//clear caches
	if (TexturesCache) delete TexturesCache;
	if (ShaderManager)delete ShaderManager;
	if (PImmediateContext) PImmediateContext->ClearState();

	if (PDepthStencil)PDepthStencil->Release();
	if (PDepthStencilView) PDepthStencilView->Release();
	if (PRenderTargetView) PRenderTargetView->Release();
	if (PSwapChain) PSwapChain->Release();
	if (PImmediateContext) PImmediateContext->Release();
	if (Pd3dDevice) Pd3dDevice->Release();
}

